package controller;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.*;

import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;

import view.LevelFilter;
import view.PacApplet;
import view.PacCanvas;
import view.PacEditorCanvas;

import model.*;

/**
 * EditorController is used to control the level editor function of 
 * Pacman's Perilous Predicament. The controller must be "attached" to both
 * a LevelEditor (which it will control) and a PacEditorCanvas (from which it
 * will receive mouse events to drive the control) to function properly.
 * 
 * @author Alexander Craig
 */
public class EditorController implements EditorListener, MouseListener,
		ActionListener, KeyListener {
	
	private static final int MAX_COLUMNS = 15;
	private static final int MAX_ROWS = 15;
	
	/** Various messages for the level creator. */ 
	private static final String SUCCESS_PANE_HEADER = "Success!";
	private static final String SAVE_LEVEL_PANE_HEADER = "Save Level";
	private static final String ERROR_PANE_HEADER = "Error";
	private static final String LEVEL_COULD_NOT_LOAD_ERROR = "Level could not be loaded.";
	private static final String FILE_EXTENSION = ".ots";
	private static final String INPUT_FILE_COULD_NOT_BE_OPEN_ERROR = "Input file could not be open. Check for invalid extension (should be .ots)";
	private static final String ERROR_EXPORTING_MSG = "Error exporting file.";
	private static final String LEVEL_EXISTS_MSG = "Level file already exists, please select another name.";
	private static final String LEVEL_SAVED_SUCCESFUL_MSG = "Level saved succesfully!";
	private static final String NAME_LEVEL_MSG = "Please select a filename for your level (without extension):";
	private static final String NO_SPAWN_POINT_MSG = "A Player spawn point must be specified before being saving a level.";
	private static final String INVALID_INPUT_MSG = "Invalid input, please try again.";
	private static final String NEW_LEVEL_MSG_HEADER = "New Level Creation";
	private static final String ROWS_MSG = "How many rows do you want in your level? (max " + MAX_ROWS + ")";
	private static final String COLUMNS_MSG = "How many columns do you want in your level? (max " + MAX_COLUMNS + ")";
	private static final String NAME_NEW_LEVEL_MSG = "What would you like to name your new level?";
	
	/** the keys used to select directions to use tools in */
	private static final char EAST_KEY = 'd';
	private static final char SOUTH_KEY = 's';
	private static final char WEST_KEY = 'a';
	private static final char NORTH_KEY = 'w';
	
	/** the instance of LevelEditor for this controller to control */
	private LevelEditor editor;
	/** the PacEditorCanvas that should receive focus after events */
	private PacEditorCanvas view;
	
	/**
	 * Generates a new instance of EditorController
	 * @param editor	the editor to control
	 * @param view	the Canvas to listen for key presses on
	 */
	public EditorController(LevelEditor editor, PacEditorCanvas view) {
		this.editor = editor;
		editor.addEditorListener(this);
		this.view = view;
		view.addMouseListener(this);
		view.addKeyListener(this);
	}
	
	@Override
	/**
	 * Selects the primary and secondary tiles in the editor, depending on
	 * whether the first or other mouse button is pressed. Also, activates the tool
	 * when clicking on an already selected tile for some tools (generally, the
	 * tools with no specific direction).
	 */
	public void mousePressed(MouseEvent e) {
		if(e.getSource() instanceof PacEditorCanvas && editor.getLevel() != null) {
			PacEditorCanvas source = (PacEditorCanvas)e.getSource();
			// Calculate the tile that was clicked
			double x = (e.getX() - source.getXCenterTransform()) / PacCanvas.TILE_SPREAD;
			double y = (e.getY() - source.getYCenterTransform()) / PacCanvas.TILE_SPREAD;
			int xTile = (int)Math.floor(x);
			int yTile = (int)Math.floor(y);
			
			if(xTile >= 0 && xTile < editor.getMaze().getMaxX() && 
					yTile >= 0 && yTile < editor.getMaze().getMaxY()) {
				if (editor.getTool() == EditorTool.ADD_TILE) {
					editor.addTile(xTile, yTile);
				} else {
					if (editor.checkSelected(xTile, yTile) &&
							(editor.getTool() == EditorTool.REMOVE_TILE ||
							 editor.getTool() == EditorTool.ADD_SPAWN ||
							 editor.getTool() == EditorTool.CLEAR_SPAWN)) {
						editor.useTool();
					} else if (e.getButton() == MouseEvent.BUTTON1) {
						editor.setSelectedTile(xTile, yTile);
					} else {
						editor.setSecondaryTile(xTile, yTile);
					}
				}
			}
		}
	}
	
	@Override
	/**
	 * Sets the editor's current tool based on the action command passed, then requests focus
	 * on the view (prevents user having to reclick into the view)
	 * Special Cases: If a NEW_LEVEL tool command is sent, this function asks for user input,
	 * then asks the editor to generate the new level.
	 * If a SET_PASS_DOTS is sent, this generates a dialog box, and sets the pass dots
	 * accordingly.
	 */
	public void actionPerformed(ActionEvent e) {
		String command = e.getActionCommand();
		
		// If a JComboBox sent the event, get the selected item and set it as the spawn type
		if( e.getSource() instanceof JComboBox && e.getActionCommand() == EditorTool.SET_SPAWN_TYPE.toString()) {
			JComboBox comboBox = (JComboBox)e.getSource();
			command = (String)comboBox.getSelectedItem();
			for(GameEntity ent : GameEntity.values()) {
				if(ent.toString() == command) {
					editor.setSpawnType(ent);
				}
			}
		} 
		
		if(command == EditorTool.NEW_LEVEL.toString()) {
			generateNewLevel();
		} else if (command == EditorTool.SAVE_LEVEL.toString()) {
			JOptionPane.showMessageDialog(null, PacApplet.NOT_AVAILABLE, PacApplet.NOT_AVAILABLE_TITLE, JOptionPane.INFORMATION_MESSAGE);
		} else if (command == EditorTool.LOAD_LEVEL.toString()) {
			JOptionPane.showMessageDialog(null, PacApplet.NOT_AVAILABLE, PacApplet.NOT_AVAILABLE_TITLE, JOptionPane.INFORMATION_MESSAGE);
		}
		else {
			// Check to see if the passed string corresponds to a tool, and set the editor tool if so
			for(EditorTool tool : EditorTool.values()) {
				if (command == tool.toString()) {
					editor.setTool(tool);
				}
			}
		}
		
		// Request focus on the PacEditorCanvas
		view.requestFocus();
	}
	
	/**
	 * Gets user input for exact specifications, and generates a new level if they are valid
	 */
	private void generateNewLevel() {
		String title = "";
		int columns = 0;
		int rows = 0;
		boolean valid = true;
		try {
			title = JOptionPane.showInputDialog(view, NAME_NEW_LEVEL_MSG, NEW_LEVEL_MSG_HEADER,
					JOptionPane.QUESTION_MESSAGE);
			if(title == null || title == "") {
				return;
			} else {
				columns = Integer.parseInt(JOptionPane.showInputDialog(view, COLUMNS_MSG, NEW_LEVEL_MSG_HEADER,
						JOptionPane.QUESTION_MESSAGE));
				rows = Integer.parseInt(JOptionPane.showInputDialog(view, ROWS_MSG, NEW_LEVEL_MSG_HEADER,
						JOptionPane.QUESTION_MESSAGE));
			}
		} catch (Exception err) {
			valid = false;
		}
		if (!valid || rows <= 0 || rows > MAX_ROWS || columns <= 0 || columns > MAX_COLUMNS) {
			JOptionPane.showMessageDialog(view, INVALID_INPUT_MSG, ERROR_PANE_HEADER, JOptionPane.ERROR_MESSAGE);
			return;
		} else {
			editor.newLevel(title, columns, rows);
		}
	}

	@Override
	/**
	 * Sets the direction to use a tool in based on the key pressed, then
	 * uses the tool.
	 */
	public void keyPressed(KeyEvent e) {
		boolean valid = false;
		if (e.getKeyChar() == NORTH_KEY) {
			editor.setDirection(Direction.NORTH);
			valid = true;
		} else if (e.getKeyChar() == EAST_KEY) {
			editor.setDirection(Direction.EAST);
			valid = true;
		} else if (e.getKeyChar() == SOUTH_KEY) {
			editor.setDirection(Direction.SOUTH);
			valid = true;
		}  else if (e.getKeyChar() == WEST_KEY) {
			editor.setDirection(Direction.WEST);
			valid = true;
		}
		if (valid) {
			editor.useTool();
		}
	}

	@Override
	public void keyReleased(KeyEvent e) {}
	@Override
	public void keyTyped(KeyEvent e) {}
	@Override
	public void toolChange(EditorEvent event) {}
	@Override
	public void mouseClicked(MouseEvent e) {}
	@Override
	public void mouseEntered(MouseEvent e) {}
	@Override
	public void mouseExited(MouseEvent e) {}
	@Override
	public void mouseReleased(MouseEvent e) {}
	@Override
	public void mazeChange(EditorEvent event) {}
	@Override
	public void newLevel(EditorEvent event) {}
	@Override
	public void selectionChange(EditorEvent event) {}
}
